/* Copyright (c) 2022-2022, LiWangQian<liwangqian@huawei.com> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef LOGPP_REPEAT_FILTER_H
#define LOGPP_REPEAT_FILTER_H

#include "logpp/configs.h"
#include "logpp/filters/ifilter.h"
#include "logpp/filters/log_key.h"
#include "logpp/filters/log_record.h"
#include "logpp/stl/list.h"
#include "logpp/stl/unordered_map.h"

LOGPP_NS_BEGIN

class repeat_filter : public ifilter {
public:
    repeat_filter(uint32_t max_cached_log_count, const time::seconds &repeat_period)
        : max_cached_log_count_{max_cached_log_count}
        , repeat_period_{repeat_period}
    {}

    uint32_t log_count()                 const noexcept { return log_count_;            }
    uint32_t max_cached_log_count()      const noexcept { return max_cached_log_count_; }
    const time::seconds &repeat_period() const noexcept { return repeat_period_;        }

    bool is_cached_full() const noexcept { return log_count_ >= max_cached_log_count_;  }

    bool filter(message &m) noexcept override
    {
        if (!m.valid()) return true;

        return filter_message(m);
    }

private:

    bool filter_message(message &m)
    {
        log_key key{make_hash({m.content(), m.length()}), m.context().line()};
        auto it = logs_map_.find(key);
        if (it != logs_map_.end()) {
            return update_cached_log(key, it->second);
        }
        if (is_cached_full()) {
            remove_oldest_cached_log();
        }
        return cache_new_log(key);
    }

    bool update_cached_log(const log_key &key, log_record &rec)
    {
        log_list_move_to_front(key);
        auto last_time = rec.last_update_time();
        auto now = time::clock::now();
        rec.update(now);
        return is_in_repeat_period(last_time, now);
    }

    void log_list_move_to_front(const log_key &key)
    {
        logs_list_.erase(std::find(logs_list_.begin(), logs_list_.end(), key));
        logs_list_.push_back(key);
    }

    bool is_in_repeat_period(const time::timepoint &last,
        const time::timepoint &now) noexcept
    {
        return (now - last) < repeat_period_;
    }

    bool cache_new_log(const log_key &key)
    {
        ++log_count_;
        logs_map_.emplace(key, log_record{});
        logs_list_.push_back(key);
        return false;
    }

    void remove_oldest_cached_log()
    {
        auto &oldest_key = logs_list_.front();
        logs_list_.pop_front();
        logs_map_.erase(oldest_key);
        --log_count_;
    }

    stl::unordered_map<log_key, log_record> logs_map_;
    stl::list<log_key> logs_list_;
    uint32_t log_count_{0};
    uint32_t max_cached_log_count_{0};
    time::seconds repeat_period_;
};

static inline auto make_repeat_filter(uint32_t max_cached_log_count,
    const time::seconds &repeat_period)
{
    return new_object<repeat_filter>(max_cached_log_count, repeat_period);
}

LOGPP_NS_END

#endif
